Eins der größten Ziele in 3D Applikationen ist Geschwindigkeit. Sie sollten die Anzahl der Polygone, die Sie tatsächlich rendern immer limitieren, etweder durch Sortierung, Culling oder Level-of-Detail Algorithmen. Wie dem auch sei, wenn alles andere fehl schlägt und Sie rohe Polygon-Pushing-Power benötigen, können Sie sich die Optimierungen zu nutze machen, die Ihnen von OpenGL zur Verfügung gestellt werden. Vertex Arrays sind ein guter Weg um das zu machen plus ein paar neue Extensionen der Graifkkartern, namens Vertex Buffer Objekte lässt den Traum einer jeden FPS Steigerung wahr werden. Die Extension ARB_vertex_buffer_object funktioniert wie ein Vertex Array, mit dem Unterschied, dass es die Daten in den Grafikkarten High-Performance-Speicher lädt, was die Rendering-Zeit drastisch reduziert. Da die Extension relativ neu ist, unterstüzen logischerweise nicht alle Karte diese, weshalb wir das überprüfen müssen.
In diesem Tutorial werden wir
#define MESH_RESOLUTION 4.0f // Pixel Pro Vertex #define MESH_HEIGHTSCALE 1.0f // Mesh Höhen Skalierung //#define NO_VBOS // wenn definiert, werden VBOs ausgeschaltet
// VBO Extension Definitionen, aus der glext.h #define GL_ARRAY_BUFFER_ARB 0x8892 #define GL_STATIC_DRAW_ARB 0x88E4 typedef void (APIENTRY * PFNGLBINDBUFFERARBPROC) (GLenum target, GLuint buffer); typedef void (APIENTRY * PFNGLDELETEBUFFERSARBPROC) (GLsizei n, const GLuint *buffers); typedef void (APIENTRY * PFNGLGENBUFFERSARBPROC) (GLsizei n, GLuint *buffers); typedef void (APIENTRY * PFNGLBUFFERDATAARBPROC) (GLenum target, int size, const GLvoid *data, GLenum usage); // VBO Extension Funktionszeiger PFNGLGENBUFFERSARBPROC glGenBuffersARB = NULL; // VBO Namens Generations-Prozedur PFNGLBINDBUFFERARBPROC glBindBufferARB = NULL; // VBO Bind-Prozedur PFNGLBUFFERDATAARBPROC glBufferDataARB = NULL; // VBO Daten-Lade-Prozedur PFNGLDELETEBUFFERSARBPROC glDeleteBuffersARB = NULL; // VBO Lösch-Prozedur
class CVert // Vertex Klasse
{
public:
float x; // X Komponente
float y; // Y Komponente
float z; // Z Komponente
};
typedef CVert CVec; // Die Definitionen sind synonym
class CTexCoord // Texturkoordinaten Klasse
{
public:
float u; // U Komponente
float v; // V Komponente
};
class CMesh
{
public:
// Mesh Daten
int m_nVertexCount; // Vertex Anzahl
CVert* m_pVertices; // Vertex Daten
CTexCoord* m_pTexCoords; // Texturkoordinaten
unsigned int m_nTextureId; // Textur ID
// Vertex Buffer Objekt Namen
unsigned int m_nVBOVertices; // Vertex VBO Name
unsigned int m_nVBOTexCoords; // Texturkoordinaten VBO Name
// Temporäre Daten
AUX_RGBImageRec* m_pTextureImage; // Heightmap Daten
public:
CMesh(); // Mesh Konstruktor
~CMesh(); // Mesh Destruktor
// Heightmap Loader
bool LoadHeightmap( char* szPath, float flHeightScale, float flResolution );
// einzelne Punkt Höhe
float PtHeight( int nX, int nY );
// VBO Build Funktion
void BuildVBOs();
};
bool g_fVBOSupported = false; // wird ARB_vertex_buffer_object unterstützt? CMesh* g_pMesh = NULL; // Mesh Daten float g_flYRot = 0.0f; // Rotation int g_nFPS = 0, g_nFrames = 0; // FPS und FPS Zähler DWORD g_dwLastFPS = 0; // Letzte gemessene FPS Zeit
bool CMesh :: LoadHeightmap( char* szPath, float flHeightScale, float flResolution )
{
// Fehler-Überprüfung
FILE* fTest = fopen( szPath, "r" ); // Öffne das Image
if( !fTest ) // stelle sicher, das es gefunden wurde
return false; // wenn nicht, fehlt die Datei
fclose( fTest ); // Handle wird nicht mehr benötigt
// Lade Textur Daten
m_pTextureImage = auxDIBImageLoad( szPath ); // benutze GLaux's Bitmap Lade Routine
// Generiere Vertex Feld
m_nVertexCount = (int) ( m_pTextureImage->sizeX * m_pTextureImage->sizeY * 6 / ( flResolution * flResolution ) );
m_pVertices = new CVec[m_nVertexCount]; // Alloziiere Vertex Daten
m_pTexCoords = new CTexCoord[m_nVertexCount]; // Alloziiere Texturkoordinaten-Daten
int nX, nZ, nTri, nIndex=0; // erzeuge Variablen
float flX, flZ;
for( nZ = 0; nZ < m_pTextureImage->sizeY; nZ += (int) flResolution )
{
for( nX = 0; nX < m_pTextureImage->sizeX; nX += (int) flResolution )
{
for( nTri = 0; nTri < 6; nTri++ )
{
// mit diesem schnellen Hack finden wir die X,Z Position des Punktes heraus
flX = (float) nX + ( ( nTri == 1 || nTri == 2 || nTri == 5 ) ? flResolution : 0.0f );
flZ = (float) nZ + ( ( nTri == 2 || nTri == 4 || nTri == 5 ) ? flResolution : 0.0f );
// Setze die Daten, benutze PtHeight um den Y-Wert zu erhalten
m_pVertices[nIndex].x = flX - ( m_pTextureImage->sizeX / 2 );
m_pVertices[nIndex].y = PtHeight( (int) flX, (int) flZ ) * flHeightScale;
m_pVertices[nIndex].z = flZ - ( m_pTextureImage->sizeY / 2 );
// strecke die Textur über das gesamte Mesh
m_pTexCoords[nIndex].u = flX / m_pTextureImage->sizeX;
m_pTexCoords[nIndex].v = flZ / m_pTextureImage->sizeY;
// Inkrementiere unseren Index
nIndex++;
}
}
}
// Lade die Textur
glGenTextures( 1, &m_nTextureId ); // ermittle eine freie ID
glBindTexture( GL_TEXTURE_2D, m_nTextureId ); // Binde die Textur
glTexImage2D( GL_TEXTURE_2D, 0, 3, m_pTextureImage->sizeX,
m_pTextureImage->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE,
m_pTextureImage->data );
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
// gebe die Textur-Daten wieder frei
if( m_pTextureImage )
{
if( m_pTextureImage->data )
free( m_pTextureImage->data );
free( m_pTextureImage );
}
return true;
}
float CMesh :: PtHeight( int nX, int nY )
{
// Berechne die Position in der Textur, darauf achten, dass keine Überlauf stattfindet
int nPos = ( ( nX % m_pTextureImage->sizeX ) + ( ( nY % m_pTextureImage->sizeY ) * m_pTextureImage->sizeX ) ) * 3;
float flR = (float) m_pTextureImage->data[ nPos ]; // ermittle die rote Komponente
float flG = (float) m_pTextureImage->data[ nPos + 1 ]; // ermittle die grüne Komponente
float flB = (float) m_pTextureImage->data[ nPos + 2 ]; // ermittle die blaue Komponente
return ( 0.299f * flR + 0.587f * flG + 0.114f * flB ); // berechne die Höhe mit dem Luminance Algorithmus
}
void CMesh :: BuildVBOs()
{
// Generiere und Binde den Vertex Buffer
glGenBuffersARB( 1, &m_nVBOVertices ); // ermittle einen gültigen Namen
glBindBufferARB( GL_ARRAY_BUFFER_ARB, m_nVBOVertices ); // Binde den Buffer
// Lade die Daten
glBufferDataARB( GL_ARRAY_BUFFER_ARB, m_nVertexCount*3*sizeof(float), m_pVertices, GL_STATIC_DRAW_ARB );
// Generiere und Binde den Textur Koordinaten Buffer
glGenBuffersARB( 1, &m_nVBOTexCoords ); // ermittle einen gültigen Namen
glBindBufferARB( GL_ARRAY_BUFFER_ARB, m_nVBOTexCoords ); // Binde den Buffer
// Lade die Daten
glBufferDataARB( GL_ARRAY_BUFFER_ARB, m_nVertexCount*2*sizeof(float), m_pTexCoords, GL_STATIC_DRAW_ARB );
// unsere Kopie der Daten wird nicht länger benötigt, diese sind sicher in der Grafikkarte
delete [] m_pVertices; m_pVertices = NULL;
delete [] m_pTexCoords; m_pTexCoords = NULL;
}
// Lade die Mesh Daten
g_pMesh = new CMesh(); // Instanziiere unser Mesh
if( !g_pMesh->LoadHeightmap( "terrain.bmp", // Lade unsere Heightmap
MESH_HEIGHTSCALE, MESH_RESOLUTION ) )
{
MessageBox( NULL, "Error Loading Heightmap", "Error", MB_OK );
return false;
}
// überprüfe ob VBOs unterstützt werden
#ifndef NO_VBOS
g_fVBOSupported = IsExtensionSupported( "GL_ARB_vertex_buffer_object" );
if( g_fVBOSupported )
{
// ermittle Zeiger auf die GL Funktionen
glGenBuffersARB = (PFNGLGENBUFFERSARBPROC) wglGetProcAddress("glGenBuffersARB");
glBindBufferARB = (PFNGLBINDBUFFERARBPROC) wglGetProcAddress("glBindBufferARB");
glBufferDataARB = (PFNGLBUFFERDATAARBPROC) wglGetProcAddress("glBufferDataARB");
glDeleteBuffersARB = (PFNGLDELETEBUFFERSARBPROC) wglGetProcAddress("glDeleteBuffersARB");
// Lade Vertex Daten in den Grafikkartenspeicher
g_pMesh->BuildVBOs(); // erzeuge die VBOs
}
#else /* NO_VBOS */
g_fVBOSupported = false;
#endif
bool IsExtensionSupported( char* szTargetExtension )
{
const unsigned char *pszExtensions = NULL;
const unsigned char *pszStart;
unsigned char *pszWhere, *pszTerminator;
// Extension Namen sollten keine Leerzeichen enthalten
pszWhere = (unsigned char *) strchr( szTargetExtension, ' ' );
if( pszWhere || *szTargetExtension == '\0' )
return false;
// ermittle Extensions String
pszExtensions = glGetString( GL_EXTENSIONS );
// such nach einer exakten Kopie des Extensions Strings
pszStart = pszExtensions;
for(;;)
{
pszWhere = (unsigned char *) strstr( (const char *) pszStart, szTargetExtension );
if( !pszWhere )
break;
pszTerminator = pszWhere + strlen( szTargetExtension );
if( pszWhere == pszStart || *( pszWhere - 1 ) == ' ' )
if( *pszTerminator == ' ' || *pszTerminator == '\0' )
return true;
pszStart = pszTerminator;
}
return false;
}
void Draw (void)
{
glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // lösche Screen und Depth Buffer
glLoadIdentity (); // Resette die Modelview Matrix
// ermittle FPS
if( GetTickCount() - g_dwLastFPS >= 1000 ) // wenn eine Sekunden vergangen ist...
{
g_dwLastFPS = GetTickCount(); // aktualisiere unsere Zeit-Variable
g_nFPS = g_nFrames; // speichere die FPS
g_nFrames = 0; // Resette den FPS Zähler
char szTitle[256]={0}; // erzeuge den Titel-String
sprintf( szTitle, "Lesson 45: NeHe & Paul Frazee's VBO Tut - %d
Triangles, %d FPS", g_pMesh->m_nVertexCount / 3, g_nFPS );
if( g_fVBOSupported ) // füge eine Notiz über VBOs ein
strcat( szTitle, ", Using VBOs" );
else
strcat( szTitle, ", Not Using VBOs" );
SetWindowText( g_window->hWnd, szTitle ); // Setze den Titel
}
g_nFrames++; // Inkrementiere unseren FPS Zähler
// bewege die Kamera
glTranslatef( 0.0f, -220.0f, 0.0f ); // bewege über das Terrain
glRotatef( 10.0f, 1.0f, 0.0f, 0.0f ); // schaue etwas runter
glRotatef( g_flYRot, 0.0f, 1.0f, 0.0f ); // Rotiere die Kamera
// Setze Pointer auf unsere Daten
if( g_fVBOSupported )
{
glBindBufferARB( GL_ARRAY_BUFFER_ARB, g_pMesh->m_nVBOVertices );
glVertexPointer( 3, GL_FLOAT, 0, (char *) NULL ); // Setze den Vertex Pointer auf den Vertex Buffer
glBindBufferARB( GL_ARRAY_BUFFER_ARB, g_pMesh->m_nVBOTexCoords );
glTexCoordPointer( 2, GL_FLOAT, 0, (char *) NULL ); // Setze den TexCoord Pointer auf den TexCoord Buffer
} else
{
glVertexPointer( 3, GL_FLOAT, 0, g_pMesh->m_pVertices ); // Setze den Vertex Pointer auf unsere Vertex Daten
glTexCoordPointer( 2, GL_FLOAT, 0, g_pMesh->m_pTexCoords ); // Setze den Vertex Pointer auf unsere TexCoord Daten
}
// Render glDrawArrays( GL_TRIANGLES, 0, g_pMesh->m_nVertexCount ); // zeichne alle Dreiecke auf einmal
// deaktivere Pointer glDisableClientState( GL_VERTEX_ARRAY ); // deaktviere Vertex Arrays glDisableClientState( GL_TEXTURE_COORD_ARRAY ); // deaktiviere Texturkoordinaten Arrays }